Static NAT + Masquerading / Static NAT + Port Forwarding#1548
Conversation
There was a problem hiding this comment.
Pull request overview
This PR extends the dataplane NAT pipeline to support combinations like static NAT + masquerading and static NAT + port forwarding by (a) making static NAT direction-aware, (b) persisting “initial” flow identity in packet metadata for later flow creation, and (c) reordering/propagating NAT requirements consistently through flow lookup/filtering and flow state.
Changes:
- Split static NAT requirements into source vs destination (FlowFilter + PacketMeta flags) and track NAT application as src/dst NATed.
- Persist an initial FlowKey on
PacketMetaand propagate static-NAT requirement flags intoFlowInfoso later packets rehydrate requirements from the flow table. - Refactor stateless NAT into a dedicated NF module, add TCP/UDP transport wrappers, and move static NAT stage earlier in the dataplane pipeline.
Reviewed changes
Copilot reviewed 37 out of 38 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| stats/src/dpstats.rs | Updated access to src_vpcd via accessor after metadata encapsulation changes. |
| net/src/tcp_udp.rs | Added TCP/UDP protocol-agnostic wrapper types (TcpUdp, TcpUdpMut). |
| net/src/packet/test_utils.rs | Ensured test packet builders update embedded flow key after port changes. |
| net/src/packet/mod.rs | Added Packet::update_flow_key() and initialize meta.flow_key on parse. |
| net/src/packet/meta.rs | Split NAT flags into src/dst, privatized src_vpcd, added flow key + flow-flag computation helpers. |
| net/src/packet/display.rs | Updated metadata display for new NAT flags + src_vpcd() accessor. |
| net/src/lib.rs | Exported new tcp_udp module. |
| net/src/headers/mod.rs | Added TryTcpUdp/TryTcpUdpMut traits and delegated implementations. |
| net/src/flows/flow_key.rs | Updated flow-key derivation to use PacketMeta::src_vpcd() accessor. |
| net/src/flows/flow_info.rs | Introduced FlowInfoFlags and extended related_pair() to store per-flow flags. |
| nat/src/test.rs | Added end-to-end tests for static NAT + masquerade and static NAT + port forwarding. |
| nat/src/stateless/test.rs | Updated tests to use new static NAT direction flags + set_src_vpcd. |
| nat/src/stateless/setup/tables.rs | Tightened mapping lookups (unicast check; reject port 0) and updated types. |
| nat/src/stateless/setup/mod.rs | Adjusted imports after stateless NAT refactor. |
| nat/src/stateless/nf.rs | New stateless NAT NF implementation supporting directional static NAT + ICMP inner translation. |
| nat/src/stateless/mod.rs | Refactored stateless NAT module to re-export new NF implementation. |
| nat/src/stateful/test.rs | Updated tests for src_vpcd() accessor + set_src_vpcd. |
| nat/src/stateful/packet.rs | Mark packets as src/dst NATed when stateful NAT modifies them. |
| nat/src/stateful/nf.rs | Use initial vs current flow keys for session creation; store static-NAT requirement flags into flows. |
| nat/src/stateful/icmp_handling.rs | Updated src_vpcd() accessor usage. |
| nat/src/portfw/test.rs | Updated tests to use set_src_vpcd. |
| nat/src/portfw/portfwtable/access.rs | Added helper to rebuild port-forwarding rules directly from validated VPC table. |
| nat/src/portfw/packet.rs | Added debug asserts to prevent double NAT; (see comments re: metadata marking/message). |
| nat/src/portfw/nf.rs | Updated src_vpcd() usage and threaded static-NAT flow flags into FlowInfo::related_pair. |
| nat/src/portfw/icmp_handling.rs | Updated src_vpcd() accessor usage. |
| nat/src/portfw/flow_state.rs | Build port-forwarding flow keys using stored initial flow key and current packet key. |
| nat/src/lib.rs | Included new NAT test module. |
| nat/src/icmp_handler/nf.rs | Updated src_vpcd() accessor usage. |
| flow-filter/src/tests.rs | Updated tests to reference requires_static_nat() semantics and set_src_vpcd. |
| flow-filter/src/tables.rs | Split “requires stateless nat” into src vs dst static-NAT requirements. |
| flow-filter/src/lib.rs | Set directional static NAT flags on packets and rehydrate static-NAT requirements from FlowInfoFlags. |
| flow-filter/Cargo.toml | Added bitflags dependency for flow info flags. |
| flow-entry/src/flow_table/nf_lookup.rs | Updated flow creation helper calls for new related_pair() signature. |
| dataplane/src/packet_processor/mod.rs | Reordered pipeline so static NAT runs before port forwarding. |
| dataplane/src/packet_processor/ipforward.rs | Updated VNI annotation to use set_src_vpcd. |
| config/src/external/overlay/vpcpeering.rs | Removed unused has_nat() helper. |
| config/src/external/overlay/vpc.rs | Updated NAT-mode compatibility validation to allow static NAT alongside other modes on the opposite side. |
| Cargo.lock | Added transitive lockfile updates for new dependency usage. |
Comments suppressed due to low confidence (1)
nat/src/portfw/packet.rs:103
confidence: 8
tags: [logic]
`dnat_packet` can modify the packet's destination IP/port, but it never sets `PacketMeta`'s `dst_natted` flag when it does so. This makes `PacketMeta::is_dst_natted()` / display output inaccurate and weakens the intended "double NAT" protections. Set `dst_natted(true)` when `modified` is true.
debug_assert!(
!packet.meta().is_dst_natted(),
"Trying to apply double source NAT to packet!"
);
</details>
e71a365 to
41463b9
Compare
41463b9 to
1bee3c4
Compare
|
Adding the flow key to the packet metadata increases the size of the metadata and hence the packet. With its 1,000 stages each adding a copy to the stack, I'll look into reducing the size of the metadata tomorrow. |
4bea4c4 to
1d0ea0e
Compare
0bce3d4 to
a36a1c4
Compare
daniel-noland
left a comment
There was a problem hiding this comment.
other than some truly tiny nits this is ready to roll. I don't even think those nits need to be addressed in this PR, just noted for future. Nice work.
|
[This was a simple rebase, to get a clear diff when I push actual changes] |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR refactors NAT handling: introduces directional static NAT (src/dst) with a reader-backed StatelessNat NF, updates flow/packet flags and flow-key contracts, hardens stateful and port-forwarding flow-key logic, rewires the pipeline to run static NAT before port-forwarding, adjusts overlay validation/tests, and adds end-to-end static+masq/portfw/ICMP tests. ChangesStatic NAT and NAT pipeline behavior update
Sequence Diagram(s)sequenceDiagram
participant Ingress
participant FlowFilter
participant StaticNat
participant PortForwarding
participant StatefulMasquerade
participant ICMPHandler
participant Egress
Ingress->>FlowFilter: determine dst_vpcd, set static_nat_src/dst flags, maybe tag flow_key
FlowFilter->>StaticNat: packet with static NAT requirements
StaticNat->>StaticNat: lookup per-VNI tables, translate src/dst, set src_natted/dst_natted
StaticNat->>ICMPHandler: forward ICMP error packets for inner translation
StaticNat->>PortForwarding: post-static-NAT packet
PortForwarding->>PortForwarding: build flow-keys (fallible), apply port map
PortForwarding->>StatefulMasquerade: packet for masquerade (if required)
StatefulMasquerade->>Egress: final translated packet
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsStopped waiting for pipeline failures after 30000ms. One of your pipelines takes longer than our 30000ms fetch window to run, so review may not consider pipeline-failure results for inline comments if any failures occurred after the fetch window. Increase the timeout if you want to wait longer or run a Comment |
1169402 to
3cc328f
Compare
|
@Fredi-raspall Can you please look at the latest changes? |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
config/src/external/overlay/validation_tests.rs (1)
1214-1214: ⚡ Quick winRename tests to match the new expectation.
These tests now assert success, but function names still say
_rejected, which makes failures and test reports misleading. Please rename them to_passes.Suggested rename
-fn test_stateless_plus_stateful_rejected() { +fn test_stateless_plus_stateful_passes() { ... -fn test_stateless_plus_port_forwarding_rejected() { +fn test_stateless_plus_port_forwarding_passes() {Also applies to: 1245-1245
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@config/src/external/overlay/validation_tests.rs` at line 1214, Rename the test functions whose names end with `_rejected` to `_passes` to reflect their updated assertions: change `test_stateless_plus_stateful_rejected` to `test_stateless_plus_stateful_passes` and the other similar function at the second location (previously named with `_rejected`) to use `_passes`; update any references and test attributes (e.g., `#[test]`) so the test names and failure reports match the new "passes" expectation.nat/src/stateless/test.rs (1)
605-610: ⚡ Quick winThese helpers never exercise the new per-direction NAT flags.
Both helpers unconditionally enable source and destination static NAT, so the suite can still pass if one of the direction-specific gates regresses. Please parameterize the flags here or add src-only/dst-only cases on top of these helpers.
Also applies to: 823-828
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/stateless/test.rs` around lines 605 - 610, The test helpers that call build_test_ipv4_packet currently always enable both per-direction NAT flags via packet.meta_mut().set_static_nat_src(true) and set_static_nat_dst(true), so they never exercise src-only or dst-only behavior; update the helper(s) to accept parameters (e.g., static_nat_src: bool, static_nat_dst: bool) and set those flags based on the parameters, or add two new focused tests that call the helper and set only packet.meta_mut().set_static_nat_src(true) / set_static_nat_dst(false) and vice versa to cover src-only and dst-only paths; ensure the code paths using packet.meta_mut(), build_test_ipv4_packet, and any callers are updated to pass the new parameters or the new tests are added alongside the existing ones.nat/src/icmp_handler/nf.rs (1)
226-227: 💤 Low valueConsider clarifying why both src and dst static NAT flags are set.
Setting both
set_static_nat_src(true)andset_static_nat_dst(true)unconditionally may be confusing to future maintainers. Based on the earlier review discussion, this is safe because the StatelessNat processor returns a no-op when no mapping exists for a direction. However, this invariant isn't obvious from the code.Consider adding a brief comment explaining that both flags are set because the direction needing static NAT depends on the peering configuration, and the StatelessNat stage will only apply translations for directions with actual mappings.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/icmp_handler/nf.rs` around lines 226 - 227, The code unconditionally sets both packet.meta_mut().set_static_nat_src(true) and packet.meta_mut().set_static_nat_dst(true), which is non-obvious to readers; add a concise inline comment next to these calls explaining that both flags are set because which direction requires static NAT depends on peering configuration and the StatelessNat processor will perform a no-op for directions without a mapping (so setting both is safe). Reference the packet.meta_mut() calls and the StatelessNat processor in your comment so future maintainers understand the invariant.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@flow-filter/src/lib.rs`:
- Around line 353-370: The function maybe_tag_with_flow_key currently returns
immediately if packet.meta().flow_info.is_some(), which causes stale flow-info
to prevent capturing the pre-static-NAT FlowKey; change the logic so you do not
skip tagging for packets that will be invalidated: either call
should_invalidate_flow (or perform the invalidation check used by
process_packet) before deciding to skip, or invert the guard to only skip
tagging when an active/reusable flow remains (i.e., when
meta().flow_info.is_some() AND the flow is not slated for invalidation). Update
maybe_tag_with_flow_key (and the duplicate logic around lines ~471-482) to
consult should_invalidate_flow/process_packet’s invalidation predicate and only
return early when the existing flow is still valid/active; otherwise proceed to
compute FlowKey::try_from(net::flow_key::Uni(&*packet)) and set
packet.meta_mut().flow_key as before.
In `@flow-filter/src/tests.rs`:
- Around line 45-66: The current helper functions (needs_masquerade,
needs_static_nat, needs_port_forwarding, needs_no_nat) treat static NAT as
mutually exclusive with masquerade/port-forwarding and ignore the src/dst split;
update them to be orthogonal so tests can assert stateful NAT and
port-forwarding independently from source-vs-destination static NAT. Replace the
exclusory helpers with independent predicate helpers such as
is_stateful_nat(packet) (checks packet.meta().requires_stateful_nat()),
is_static_nat_src(packet) and is_static_nat_dst(packet) (use the new src/dst
static NAT flags on packet.meta()), and is_port_forwarding(packet) or
is_port_forwarding_src/dst as appropriate (check
packet.meta().requires_port_forwarding() per direction if available); remove
combinations that assume mutual exclusion (e.g., don't AND-not static-nat inside
is_stateful_nat or is_port_forwarding) so tests can combine these predicates to
express mixed static+masquerade/port-forwarding cases.
In `@nat/src/stateless/nf.rs`:
- Around line 254-257: destination_nat() is mutating embedded ICMP-error packets
without validating checksums; mirror source_nat() by invoking
validate_checksums_icmp() before calling
Self::translate_icmp_inner_packet_src_if_any so malformed ICMP errors are not
rewritten. Locate the block using Self::is_icmp_inner_translation_candidate and
add a checksum validation guard (same function used in source_nat) that returns
early on invalid checksums, then proceed to call
translate_icmp_inner_packet_src_if_any only when validate_checksums_icmp()
succeeds.
---
Nitpick comments:
In `@config/src/external/overlay/validation_tests.rs`:
- Line 1214: Rename the test functions whose names end with `_rejected` to
`_passes` to reflect their updated assertions: change
`test_stateless_plus_stateful_rejected` to `test_stateless_plus_stateful_passes`
and the other similar function at the second location (previously named with
`_rejected`) to use `_passes`; update any references and test attributes (e.g.,
`#[test]`) so the test names and failure reports match the new "passes"
expectation.
In `@nat/src/icmp_handler/nf.rs`:
- Around line 226-227: The code unconditionally sets both
packet.meta_mut().set_static_nat_src(true) and
packet.meta_mut().set_static_nat_dst(true), which is non-obvious to readers; add
a concise inline comment next to these calls explaining that both flags are set
because which direction requires static NAT depends on peering configuration and
the StatelessNat processor will perform a no-op for directions without a mapping
(so setting both is safe). Reference the packet.meta_mut() calls and the
StatelessNat processor in your comment so future maintainers understand the
invariant.
In `@nat/src/stateless/test.rs`:
- Around line 605-610: The test helpers that call build_test_ipv4_packet
currently always enable both per-direction NAT flags via
packet.meta_mut().set_static_nat_src(true) and set_static_nat_dst(true), so they
never exercise src-only or dst-only behavior; update the helper(s) to accept
parameters (e.g., static_nat_src: bool, static_nat_dst: bool) and set those
flags based on the parameters, or add two new focused tests that call the helper
and set only packet.meta_mut().set_static_nat_src(true) /
set_static_nat_dst(false) and vice versa to cover src-only and dst-only paths;
ensure the code paths using packet.meta_mut(), build_test_ipv4_packet, and any
callers are updated to pass the new parameters or the new tests are added
alongside the existing ones.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 6d58f078-4895-4376-bbc0-22711b60e3b3
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (34)
config/src/external/overlay/tests.rsconfig/src/external/overlay/validation_tests.rsconfig/src/external/overlay/vpc.rsconfig/src/external/overlay/vpcpeering.rsdataplane/src/packet_processor/mod.rsflow-entry/src/flow_table/nf_lookup.rsflow-filter/Cargo.tomlflow-filter/src/lib.rsflow-filter/src/tables.rsflow-filter/src/tests.rsnat/src/icmp_handler/nf.rsnat/src/lib.rsnat/src/portfw/flow_state.rsnat/src/portfw/nf.rsnat/src/portfw/packet.rsnat/src/portfw/portfwtable/access.rsnat/src/stateful/nf.rsnat/src/stateful/packet.rsnat/src/stateful/test.rsnat/src/stateless/mod.rsnat/src/stateless/natrw.rsnat/src/stateless/nf.rsnat/src/stateless/setup/mod.rsnat/src/stateless/setup/tables.rsnat/src/stateless/test.rsnat/src/test.rsnet/src/flows/flow_info.rsnet/src/headers/mod.rsnet/src/lib.rsnet/src/packet/display.rsnet/src/packet/meta.rsnet/src/packet/mod.rsnet/src/tcp_udp.rspipeline/src/pipeline.rs
💤 Files with no reviewable changes (1)
- config/src/external/overlay/vpcpeering.rs
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@nat/src/stateless/nf.rs`:
- Around line 191-213: The code mutates outer source IP/port in source_nat()
(translate_src/translate_src_port) before validate_checksums_icmp(), which can
break ICMPv6 checksum validation; move the ICMP error-path checks so
is_icmp_inner_translation_candidate(packet) and validate_checksums_icmp(packet)
(and the subsequent translate_icmp_inner_packet_dst_if_any call) are performed
before any outer-source mutation, or alternately perform checksum validation
against the original outer src by capturing it prior to
translate_src/translate_src_port; ensure modified is only updated after
successful translations and that translate_icmp_inner_packet_dst_if_any still
runs when validation passes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8634a997-9fe5-4e0f-b1cd-ece93367227d
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (31)
config/src/external/overlay/tests.rsconfig/src/external/overlay/validation_tests.rsconfig/src/external/overlay/vpc.rsconfig/src/external/overlay/vpcpeering.rsdataplane/src/packet_processor/mod.rsflow-entry/src/flow_table/nf_lookup.rsflow-filter/Cargo.tomlflow-filter/src/lib.rsflow-filter/src/tables.rsflow-filter/src/tests.rsnat/src/icmp_handler/nf.rsnat/src/lib.rsnat/src/portfw/flow_state.rsnat/src/portfw/nf.rsnat/src/portfw/packet.rsnat/src/portfw/portfwtable/access.rsnat/src/stateful/nf.rsnat/src/stateful/packet.rsnat/src/stateless/mod.rsnat/src/stateless/nf.rsnat/src/stateless/setup/mod.rsnat/src/stateless/setup/tables.rsnat/src/stateless/test.rsnat/src/test.rsnet/src/flows/flow_info.rsnet/src/headers/mod.rsnet/src/lib.rsnet/src/packet/display.rsnet/src/packet/meta.rsnet/src/tcp_udp.rspipeline/src/pipeline.rs
💤 Files with no reviewable changes (1)
- config/src/external/overlay/vpcpeering.rs
✅ Files skipped from review due to trivial changes (3)
- nat/src/stateless/setup/mod.rs
- net/src/lib.rs
- config/src/external/overlay/tests.rs
🚧 Files skipped from review as they are similar to previous changes (22)
- flow-entry/src/flow_table/nf_lookup.rs
- pipeline/src/pipeline.rs
- flow-filter/Cargo.toml
- nat/src/lib.rs
- nat/src/portfw/packet.rs
- nat/src/stateful/packet.rs
- net/src/tcp_udp.rs
- nat/src/portfw/nf.rs
- flow-filter/src/lib.rs
- nat/src/portfw/flow_state.rs
- nat/src/stateless/mod.rs
- flow-filter/src/tests.rs
- flow-filter/src/tables.rs
- net/src/packet/display.rs
- net/src/headers/mod.rs
- config/src/external/overlay/validation_tests.rs
- nat/src/stateless/test.rs
- nat/src/stateless/setup/tables.rs
- nat/src/test.rs
- config/src/external/overlay/vpc.rs
- nat/src/stateful/nf.rs
- net/src/flows/flow_info.rs
To handle some NAT mode combinations, we need to keep track of the initial packet's addresses before the first NAT pass, when no flow information was found for the packet. To do this, we optionally attach a flow key to the packet's metadata. We could attach the flow key unconditionally, but this would grow the size of the metadata quite significantly (80 bytes estimated). Instead, we Box<> it, and only allocate when it's relevant: when we have a combination of static NAT with masquerading or port forwarding, and when no flow table entry was found for the packet. Note that adding even the boxed flow key makes the long_dyn_pipeline() test fail with its 1,000 stages each adding a copy of the metadata to the stack, resulting in stack overflow for the thread. Interestingly, lowering down to 999 stages seems to be enough to make it pass again. Signed-off-by: Quentin Monnet <qmo@qmon.net>
So far, using NAT on both sides of a peering was supported only if both
sides used static NAT. Combining static NAT and masquerading or port
forwarding for a connection was not possible. We want to support it now,
and for this, we need to change the way we create flow table entries.
Here are some detailed explanations, and that the packet goes through.
Let's imagine two endpoints communicating through the gateway, and both
using NAT. For this connection, endpoint 1 has IP address "a", NAT-ed to
"A"; and endpoint 2 has IP address "b", exposed with NAT as "B". Here's
a simple diagram:
+------------+ +---------+ +------------+
| | a A | | B b | |
| Endpoint 1 |------------| Gateway |------------| Endpoint 2 |
| | | | | |
+------------+ +---------+ +------------+
Let's look at a packet leaving endpoint 1 for endpoint 2:
- Initially: src: a, dst: B
- After translation: src: A, dst: b
Now the reply:
- Initially: src: b, dst: A
- After translation: src: B, dst: a
Let's consider a setup where endpoint 1 uses static NAT, and endpoint 2
uses port forwarding. When we process the packet in the port forwarding
stage, we want to create the following flow table entries:
- Key: (src: a, dst: B), translation value: (src: A, dst: b) (forward)
- Key: (src: b, dst: A), translation value: (src: B, dst: a) (reverse)
However, because the packet has already been through the static NAT
stage before reaching the port forwarding stage, we collect the
translated source IP address from the packet and create the following
entries:
- Key: (src: A, dst: B), translation value: (src: A, dst: b) (forward)
- Key: (src: b, dst: A), translation value: (src: B, dst: A) (reverse)
This is fine for the return path, but subsequent packets in the forward
direction will not match: the flow lookup stage comes before the static
NAT stage and "a" hasn't been translated to "A" yet before the flow
lookup. Flow lookup fails, we find no flow.
So instead, we want to create the forward-direction entry based not on
the address observed by the port forwarding stage, but on the initial
source IP address that the packet came with. In order to keep track of
that address, we associate a flow key for the packet's metadata. We can
use this flow key to create flow table entries for the forward
direction, but we keep using the addresses from the packet, after static
NAT has occurred, for the reverse direction.
Signed-off-by: Quentin Monnet <qmo@qmon.net>
We've stopped using this error for the case where no mapping is found for the source and destination IP addresses for the packet; we should stop using it when no mapping is found for the addresses in the inner packet for ICMP Error messages, too. We don't want to raise an error when no mapping is found, it may be that no static NAT is required for the address. At the moment this should not happen because we tag packets that should be processed with static NAT, but it may happen in the future (for ICMP Error message handling for some NAT modes combinations, when packets haven't been through the flow-filter stage yet). Signed-off-by: Quentin Monnet <qmo@qmon.net>
This is not completely ideal because we'd like to keep the possibility to rework this code in the future to enter() just once for a batch of packets, but this will help reuse the process_packet() function in the ICMP Error handler in a follow-up commit. Signed-off-by: Quentin Monnet <qmo@qmon.net>
We've recently added support for static NAT in combination with masquerade or with port forwarding, on the opposite ends of a peering. But we haven't taken care of ICMP yet! In particular, the change means that ICMP Error messages may require to go through static NAT after they have been masqueraded or port-forwarded. To do so, we simply allow ICMP Error packets to pass through the static NAT stage even if they've been NAT-ed. We also need to make sure that checksums are valid after any previous NAT processing of these packets. Whether or not the packet actually requires NAT doesn't really matter here. We don't have a good way to tell, because the packet hasn't been through the flow-filter stage (we skipped it, given that the ICMP Error handler set the dst_vpcd for the packet, because it might have an unknown source IP but we want to allow it anyway), so we need to translate as a best-effort. However, we do mark packets as source-NAT-ed or dest-NAT-ed when we process them for masquerade or port forwarding, and the static NAT stage does not re-apply translation for the indicated address, so there's no risk to incur double translations. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Now that we support using static NAT on one side of a peering and masquerading or port forwarding on the other end, let's allow users to deploy such configurations. Also adjust relevant tests accordingly. Signed-off-by: Quentin Monnet <qmo@qmon.net>
3cc328f to
a98c8c9
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
nat/src/lib.rs (1)
33-33: 💤 Low valueConsider adding
#[cfg(test)]to avoid compiling test code in non-test builds.The
mod test;declaration includes the test module unconditionally. Standard practice is to gate test modules with#[cfg(test)]so they are only compiled when running tests, reducing binary size and compile time in release builds.♻️ Suggested change
+#[cfg(test)] mod test;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@nat/src/lib.rs` at line 33, The test module is being compiled unconditionally via the `mod test;` declaration; add a test-only cfg attribute so the `test` module is only included in test builds by annotating the `mod test;` line with `#[cfg(test)]` (i.e., gate the `test` module with the test configuration) to prevent compiling tests into non-test/release builds and reduce build size/time.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@nat/src/lib.rs`:
- Line 33: The test module is being compiled unconditionally via the `mod test;`
declaration; add a test-only cfg attribute so the `test` module is only included
in test builds by annotating the `mod test;` line with `#[cfg(test)]` (i.e.,
gate the `test` module with the test configuration) to prevent compiling tests
into non-test/release builds and reduce build size/time.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 33666e8e-14ba-413f-bb53-e66e80388894
📒 Files selected for processing (15)
config/src/external/overlay/tests.rsconfig/src/external/overlay/validation_tests.rsconfig/src/external/overlay/vpc.rsconfig/src/external/overlay/vpcpeering.rsflow-filter/src/lib.rsnat/src/icmp_handler/nf.rsnat/src/lib.rsnat/src/portfw/flow_state.rsnat/src/portfw/nf.rsnat/src/portfw/portfwtable/access.rsnat/src/stateful/nf.rsnat/src/stateless/nf.rsnat/src/test.rsnet/src/packet/meta.rspipeline/src/pipeline.rs
💤 Files with no reviewable changes (1)
- config/src/external/overlay/vpcpeering.rs
🚧 Files skipped from review as they are similar to previous changes (12)
- pipeline/src/pipeline.rs
- nat/src/portfw/portfwtable/access.rs
- config/src/external/overlay/validation_tests.rs
- config/src/external/overlay/tests.rs
- nat/src/portfw/flow_state.rs
- nat/src/icmp_handler/nf.rs
- flow-filter/src/lib.rs
- nat/src/test.rs
- nat/src/stateless/nf.rs
- config/src/external/overlay/vpc.rs
- net/src/packet/meta.rs
- nat/src/stateful/nf.rs
Add unit tests to validate the use in a pipeline of static NAT + masquerade, or static NAT + port forwarding, on opposite sides of a peering. We set up a pipeline, create a packet, process it, and check for its output; then we create a fake reply, process it, check as well; at last, we re-process the original packet, to make sure that the connection still works after the flow table entry has been created. Signed-off-by: Quentin Monnet <qmo@qmon.net>
a98c8c9 to
4813fb0
Compare
|
Merging in #1550, which was independently approved, on top of this branch. |
Rather than the large translate_error() functions, implement From for DoneReason. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
FlowKey is an enum with a single Unidirectional variant wrapping FlowKeyData, the second (bidirectional) variant was removed some time ago. Turn FlowKey into a tuple struct so the wrapping no longer requires pattern-matching, and drop the now-trivial match arms in PartialEq, Hash, and the Display impl. The inner field is left pub for now, a follow-up commit will merge FlowKey and FlowKeyData into a single struct. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
FlowKey had become a tuple struct wrapping FlowKeyData, with accessors data() and data_mut() used at every call site. The two types described the same flow tuple, so we can fold FlowKeyData's fields, methods, and Hash implementation directly into FlowKey and drop the indirection. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
FlowKey used to support a bidirectional variant alongside the unidirectional one, and the builder was named uni() to disambiguate. Bidirectional keys were dropped a while ago, and the remaining builder named "uni()" is now confusing. Rename it to "new()" instead. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
Uni<T> existed to tag a packet for unidirectional FlowKey extraction back when bidirectional keys were also supported. Bidirectional keys are gone, so Uni is not longer necessary, it simply adds dead weight that every call site has to thread through, and makes the code less clear. Convert the TryFrom impl to accept &Packet<Buf> directly, delete the Uni struct, and drop the wrapper at the call sites. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Quentin Monnet <qmo@qmon.net>
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
net/src/flows/flow_key.rs (1)
578-585:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
FlowKeyhash/eq contract is inconsistent for TCP/UDP port swaps.
FlowKey::hashis directional, butTcpProtoKey/UdpProtoKeyequality is symmetric. For same-IP flows, reversed ports can compare equal while producing different hashes, which can break hash-map lookups/removals.🛠️ Proposed fix
impl PartialEq for TcpProtoKey { fn eq(&self, other: &Self) -> bool { - self.symmetric_eq(other) + self.src_port == other.src_port && self.dst_port == other.dst_port } } impl PartialEq for UdpProtoKey { fn eq(&self, other: &Self) -> bool { - self.symmetric_eq(other) + self.src_port == other.src_port && self.dst_port == other.dst_port } }As per coding guidelines, "Find logic errors in the code under review. If confident that code is incorrect, suggest a fix."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@net/src/flows/flow_key.rs` around lines 578 - 585, The FlowKey Hash implementation is directional while TcpProtoKey/UdpProtoKey equality treats port pairs symmetrically, so implement a canonical (order-independent) hashing for the protocol-port part: in the Hash for FlowKey (the impl Hash for FlowKey function) replace the directional calls to proto_key_info.hash_src(state) and proto_key_info.hash_dst(state) with a single canonical hashing step that orders the (src_ip, src_port) vs (dst_ip, dst_port) pair deterministically (e.g., compare src_ip/dst_ip and for equal IPs compare ports or simply use min/max for ports) and then hash the protocol + ordered port pair; this keeps FlowKey::hash consistent with TcpProtoKey/UdpProtoKey equality and fixes map lookup/removal mismatches.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@nat/src/icmp_handler/icmp_error_msg.rs`:
- Around line 50-52: The match arm mapping
IcmpErrorMsgError::NoTranslationPossible to DoneReason::InternalFailure is
incorrect; change that arm to return the semantic DoneReason for expected
non-translatable packets (e.g. DoneReason::NoTranslationPossible or
DoneReason::NoTranslation if that’s the existing enum variant) instead of
InternalFailure, update the match to use the correct DoneReason variant in the
function handling IcmpErrorMsgError, and adjust/add tests to assert the new
mapping for NoTranslationPossible.
---
Outside diff comments:
In `@net/src/flows/flow_key.rs`:
- Around line 578-585: The FlowKey Hash implementation is directional while
TcpProtoKey/UdpProtoKey equality treats port pairs symmetrically, so implement a
canonical (order-independent) hashing for the protocol-port part: in the Hash
for FlowKey (the impl Hash for FlowKey function) replace the directional calls
to proto_key_info.hash_src(state) and proto_key_info.hash_dst(state) with a
single canonical hashing step that orders the (src_ip, src_port) vs (dst_ip,
dst_port) pair deterministically (e.g., compare src_ip/dst_ip and for equal IPs
compare ports or simply use min/max for ports) and then hash the protocol +
ordered port pair; this keeps FlowKey::hash consistent with
TcpProtoKey/UdpProtoKey equality and fixes map lookup/removal mismatches.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 9b951645-ee91-447b-b527-f1f53408b6c5
📒 Files selected for processing (14)
flow-entry/src/flow_table/nf_lookup.rsflow-entry/src/flow_table/table.rsflow-filter/src/lib.rsflow-filter/src/tests.rsnat/src/icmp_handler/icmp_error_msg.rsnat/src/portfw/flow_state.rsnat/src/stateful/allocation.rsnat/src/stateful/flows.rsnat/src/stateful/nf.rsnat/src/stateful/test.rsnat/src/stateless/nf.rsnet/src/flows/display.rsnet/src/flows/flow_key.rsnet/src/lib.rs
🚧 Files skipped from review as they are similar to previous changes (5)
- nat/src/portfw/flow_state.rs
- flow-filter/src/tests.rs
- flow-filter/src/lib.rs
- nat/src/stateless/nf.rs
- nat/src/stateful/nf.rs
The first commit are some preparatory clean-ups for the static NAT code:
Then we add some finer control for running static NAT on a packet:
We proceed to the changes we need to support combinations of NAT modes:
We add support for ICMP Error messages with the NAT combinations (thanks Fredi!)
We update the validation step and add tests:
Please refer to individual commit description for details.
Summary
This PR refactors static NAT into a dedicated StatelessNat NF and extends NAT to support directional static NAT combined with masquerading and port forwarding, plus many related fixes, refactors, and tests.
Key Changes
Core refactor & new NF
Transport & header helpers
Packet metadata & directional NAT
Pipeline & flow-info changes
Stateless NAT implementation & ICMP
Port-forwarding & stateful changes
Configuration & tests
FlowKey / public-surface & other refactors
API & public-surface changes
Error mapping & minor utilities
Performance / size note
Tests